package com.myzl.coal.client;

import com.myzl.coal.client.parse.CommonParseService;
import com.myzl.coal.domain.Device;
import com.myzl.coal.dto.SendCmdDTO;
import com.myzl.coal.mq.MqService;
import com.myzl.coal.service.protocol.ProtocolService;
import com.myzl.coal.utils.HexUtil;
import com.myzl.common.constant.HttpStatus;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.channel.socket.DatagramPacket;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.CharsetUtil;
import io.netty.util.concurrent.DefaultEventExecutorGroup;
import io.netty.util.concurrent.EventExecutorGroup;
import io.netty.util.concurrent.GlobalEventExecutor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

import java.net.InetSocketAddress;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import static com.myzl.coal.utils.HexUtil.bytesToHexString;


/**
 * @author 91941
 */
@Component
@ChannelHandler.Sharable
@Slf4j
@Data
//ChannelInboundHandler
public class BaseHandler extends ChannelInboundHandlerAdapter implements ProtocolService {

    // TODO: 2023/2/1 以下可以优化删除  保留 channelGroup按设备类型  URL_CONTEXT全量 TIME_MAP
    /**
     * tcp连接对应上线文
     */
    public static Map<String, ChannelHandlerContext> URL_CONTEXT = new ConcurrentHashMap<>();
    /**
     * 记录每个链接上一次发送报文时间
     */
    public static Map<Integer, LocalDateTime> TIME_MAP = new ConcurrentHashMap<>(16);
    public static int TIME_OUT = 3;
    /**
     * channel组 可群发消息
     */
    public static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
    private static final ConcurrentMap<String, ChannelId> URL_CHANNEL_ID = new ConcurrentHashMap<>();
    /**
     * 业务耗时线程池
     */
    public static final EventExecutorGroup group=new DefaultEventExecutorGroup(16);

    @Autowired
    public MqService mqService;
    @Autowired
    public CacheData cacheData;
    @Autowired
    public StartNettyServer startNettyServer;
    @Qualifier("parseServiceMap")
    @Autowired
    public Map<Integer, CommonParseService> parseServiceMap;


    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        try {
            if (msg instanceof DatagramPacket) {
                DatagramPacket packet = (DatagramPacket) msg;
                Integer deviceId = getDeviceId(packet.sender());

                ByteBuf byteBuf = packet.content();

                if (deviceId == null) {
                    logPrint(ctx, msg);
                    byteBuf.release();
                    return;
                }

                byte[] dataBytes = new byte[byteBuf.readableBytes()];
                byteBuf.readBytes(dataBytes);

                CommonParseService parseService = parseService(deviceId, dataBytes);
                byteBuf.release();

                if (TIME_MAP.containsKey(deviceId)) {
                    LocalDateTime lastTime = TIME_MAP.get(deviceId);
                    if (lastTime.plusMinutes(TIME_OUT).isBefore(LocalDateTime.now())) {
                        parseService.initConnect(deviceId);
                        mqService.sendDeviceStatusOffline(deviceId);
                    }
                } else {
                    mqService.sendDeviceStatusOnline(deviceId);
                }
                TIME_MAP.put(deviceId, LocalDateTime.now());
            } else {
                InetSocketAddress inetSocketAddress = (InetSocketAddress) ctx.channel().remoteAddress();
                Integer deviceId = getDeviceId(inetSocketAddress);

                if (deviceId == null) {
                    logPrint(ctx, msg);
                    return;
                }

                parseService(deviceId, msg);
                TIME_MAP.put(deviceId, LocalDateTime.now());
            }

        } catch (Exception e) {
            log.info("receive data error:", e);
        }
    }

    /**
     * 解析并更新当前时间
     */
    private CommonParseService parseService(Integer deviceId, Object dataBytes) {
        Device device = cacheData.getDevice(deviceId);
        CommonParseService parseService = parseServiceMap.get(device.getDeviceType());
        parseService.channelRead(dataBytes, deviceId);
        return parseService;
    }

    /**
     * 客户端连接会触发
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        if (ctx.channel() instanceof NioServerSocketChannel || ctx.channel() instanceof NioSocketChannel) {
            InetSocketAddress inetSocketAddress = (InetSocketAddress) ctx.channel().remoteAddress();
            setChannel(inetSocketAddress, ctx);
            Integer deviceId = getDeviceId(inetSocketAddress);
            mqService.sendDeviceStatusOnline(deviceId);
            /**设备初始化*/
            Device device = cacheData.getDevice(deviceId);
            if (device != null) {
                CommonParseService parseService = parseServiceMap.get(device.getDeviceType());
                if (parseService != null) {
                    parseService.initConnect(deviceId);
                }
            }
            log.info("open collection!,deviceId:{}", deviceId);
        }
    }

    /**
     * 客户端关闭连接会触发
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        if (channel instanceof NioSocketChannel) {
            InetSocketAddress inetSocketAddress = (InetSocketAddress) channel.remoteAddress();
            String ip = inetSocketAddress.getAddress().getHostAddress();
            int port = inetSocketAddress.getPort();
            log.info("close collection!,ip:{},port:{}", ip, port);
            URL_CONTEXT.remove(getKey(ip, port));
            Integer deviceId = getDeviceId(inetSocketAddress);
            mqService.sendDeviceStatusOffline(deviceId);
            if (Objects.nonNull(deviceId)) {
                //client 无限重连
                log.info("reLink collection!,ip:{},port:{}", ip, port);
                startNettyServer.startDevice(deviceId);
                // TODO: 2023/2/13 待测试
//                ctx.connect(inetSocketAddress);
            }
        }

    }

    /**
     * 发生异常触发
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        if (ctx.channel() instanceof NioServerSocketChannel || ctx.channel() instanceof NioSocketChannel) {
            InetSocketAddress inetSocketAddress = (InetSocketAddress) ctx.channel().remoteAddress();
            String ip = inetSocketAddress.getAddress().getHostAddress();
            int port = inetSocketAddress.getPort();
            log.error("客户端发起连接异常!,ip:" + ip + ",port:" + port, cause);
        }
        ctx.close();
    }

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        super.handlerAdded(ctx);
        Channel channel = ctx.channel();
        channelGroup.add(channel);
//        channelGroup.writeAndFlush("aaa");

    }


    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        super.userEventTriggered(ctx, evt);
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent stateEvent = (IdleStateEvent) evt;
            switch (stateEvent.state()) {
                case ALL_IDLE:
                    log.info("连接还在");
                    break;
                default:
                    log.info("默认");
            }
        }
        ctx.channel().eventLoop().execute(()->{
            System.out.println("");
        });
        group.submit(()->{
            System.out.println("");
        });

    }

    @Override
    public void logPrint(ChannelHandlerContext ctx, Object msg) {
        log.info(msg.toString());
        if (msg instanceof DatagramPacket) {
            DatagramPacket packet = (DatagramPacket) msg;
            ByteBuf byteBuf = packet.content();
            byte[] dataBytes = new byte[byteBuf.readableBytes()];
            byteBuf.readBytes(dataBytes);
            InetSocketAddress inetSocketAddress = packet.sender();
            log.info("无此设备:ip:{},port:{}", inetSocketAddress.getAddress().getHostAddress(), inetSocketAddress.getPort());
            log.debug("{} receive Data:{}", this.getClass().getSimpleName(), bytesToHexString(dataBytes));
            log.debug("{} receive Data:{}", this.getClass().getSimpleName(), packet.content().toString(CharsetUtil.UTF_8));
        } else {
            InetSocketAddress inetSocketAddress = (InetSocketAddress) ctx.channel().remoteAddress();
            log.info("无此设备:ip:{},port:{}", inetSocketAddress.getAddress().getHostAddress(), inetSocketAddress.getPort());
            if (msg instanceof String) {
                log.debug("{} receive Data:{}", this.getClass().getSimpleName(), msg);
            } else {
                byte[] dataBytes = (byte[]) msg;
                log.debug("{} receive Data:{}", this.getClass().getSimpleName(), bytesToHexString(dataBytes));
            }
        }
    }

    public static int send(SendCmdDTO sendCmdDTO) {
        try {
            Integer deviceId = sendCmdDTO.getDeviceId();
            String key = getKey(sendCmdDTO.getIp(),sendCmdDTO.getPort());
            if (URL_CONTEXT.containsKey(key)) {
                ChannelPipeline pipeline = URL_CONTEXT.get(key).pipeline();
                log.info("deviceId:{} 发送命令:{}",deviceId, HexUtil.bytesToHexString(sendCmdDTO.getCommand()));
                pipeline.writeAndFlush(sendCmdDTO.getCommand());
                return HttpStatus.SUCCESS;
            } else {
                log.info("设备已经断开连接,deviceId:{},msg:{}", deviceId, sendCmdDTO.getCommandStr());
                return HttpStatus.NOT_FOUND;
            }
        } catch (Exception e) {
            log.error("发送命令异常", e);
            return HttpStatus.ERROR;
        }
    }

//    public static int udpSend(SendCmdDTO sendCmdDTO, InetSocketAddress inetSocketAddress) {
//        try {
//            Integer deviceId = sendCmdDTO.getDeviceId();
//            String key = DEVICE_ID.get(deviceId);
//            ChannelPipeline pipeline = URL_CONTEXT.get(key).pipeline();
//            pipeline.writeAndFlush(new DatagramPacket(Unpooled.copiedBuffer(sendCmdDTO.getCommand()), inetSocketAddress));
//            return HttpStatus.SUCCESS;
//        } catch (Exception e) {
//            log.error("发送命令异常", e);
//            return HttpStatus.ERROR;
//        }
//    }

    public static void close(String key) {
        try {
            if (URL_CONTEXT.containsKey(key)) {
                ChannelPipeline pipeline = URL_CONTEXT.get(key).pipeline();
                pipeline.close();
                log.info("设备断开连接,url:{}", key);
            } else {
                log.info("设备不存在链接,url:{}", key);
            }
        } catch (Exception e) {
            log.error("关闭连接", e);
        }
    }

    public static String getKey(String ip, Integer port) {
        return (ip != null ? ip : "") + ":" + (port != null ? port : "");
    }

    public  Integer getDeviceId(InetSocketAddress inetSocketAddress) {
        String ip = inetSocketAddress.getAddress().getHostAddress();
        int port = inetSocketAddress.getPort();
        Integer deviceId = cacheData.getDeviceId(ip,port);
        return deviceId;
    }

    public static void setChannel(InetSocketAddress inetSocketAddress, ChannelHandlerContext ctx) {
        String ip = inetSocketAddress.getAddress().getHostAddress();
        int port = inetSocketAddress.getPort();
        String key = getKey(ip, port);
        if (!URL_CONTEXT.containsKey(key)) {
            URL_CONTEXT.put(key, ctx);
        }
    }

}
